import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.SpringFactoriesLoader;

import java.util.Collection;
import java.util.List;

/**
 * 自定义 PropertySourceLocator
 * 其 locate 方法将会在准备springboot上下文阶段触发，并载入到Environment;
 * 其内部聚合了 PropertiesPropertySourceLoader/YamlPropertySourceLoader 以便可以解析 properties/yml 文件
 * 其内部 new TccPropertySource() 、 TccPropertySource 初始化会使用 tccConfigClient 从 tcc 平台拉取 key 对应的 配置。
 * 然后构建成 properties
 */

public abstract class BaseTccPropertySourceLocator implements PropertySourceLocator {

    private final List<TccPlusProperties.TccConfig> tccConfigs;

    private CompositePropertySource tccPropertySources;

    // 加载 .properties 格式的配置文件
    private PropertiesPropertySourceLoader propertiesPropertySourceLoader;
    // 加载 .yaml 格式的配置文件
    private YamlPropertySourceLoader yamlPropertySourceLoader;

    // 初始化
    public BaseTccPropertySourceLocator(TccPlusProperties tccPlus) {
        // 初始化tcc 配置
        this.tccConfigs = tccPlus.getConfigs();

        // 通过 SpringBoot SPI 机制获得所有 所有 PropertySourceLoader的实例
        List<PropertySourceLoader> loaders = SpringFactoriesLoader.loadFactories(
                PropertySourceLoader.class, BaseTccPropertySourceLocator.class.getClassLoader());

        // 挑选出 加载 properties 和 yaml 格式的 loader
        for (PropertySourceLoader psl : loaders) {
            if (psl instanceof PropertiesPropertySourceLoader) {
                this.propertiesPropertySourceLoader = (PropertiesPropertySourceLoader) psl;
            }
            if (psl instanceof YamlPropertySourceLoader) {
                this.yamlPropertySourceLoader = (YamlPropertySourceLoader) psl;
            }
        }
    }

    /**
     * 准备 springboot 上下文阶段回调触发 , 获得的 PropertySource<?> 注入到 environment
     */

    @Override
    public PropertySource<?> locate(Environment ev) {
        CompositePropertySource tccPs = new CompositePropertySource("tcc-plus");
        ModuleConfigDuplicateChecker duplicateChecker = new ModuleConfigDuplicateChecker();
        for (TccPlusProperties.TccConfig tcc : tccConfigs) {
            final PropertySourceLoader loader = findLoader(tcc.getKey());
            // 内部 new TccPropertySource() TccPropertySource 初始化会使用 tccConfigClient 从 tcc 平台拉取 key 对应的 配置。
            // 然后构建成 properties
            EnumerablePropertySource<Object> ps = createTccPropertySource(tcc, loader);
            duplicateChecker.assertNotDuplicate(ps.getName(), ps.getPropertyNames());
            tccPs.addPropertySource(ps);
        }
        this.tccPropertySources = tccPs;
        return tccPs;
    }

    protected abstract EnumerablePropertySource<Object> createTccPropertySource(
            TccPlusProperties.TccConfig tcc, PropertySourceLoader loader
    );

    /**
     * 根据key后缀 获得 yaml / properties 解析器
     */
    protected PropertySourceLoader findLoader(String dataId) {
        if (yamlPropertySourceLoader != null) {
            // support yml / yaml
            for (String fileExtension : yamlPropertySourceLoader.getFileExtensions()) {
                if (dataId.endsWith(fileExtension)) {
                    return yamlPropertySourceLoader;
                }
            }
        }
        if (propertiesPropertySourceLoader != null) {
            return propertiesPropertySourceLoader;
        } else {
            throw new IllegalArgumentException(
                    "tcc: " + dataId + ", 找不到加载器. 可选的加载器为:" + loadNames()
            );
        }
    }

    private String loadNames() {
        StringBuilder sb = new StringBuilder();
        if (yamlPropertySourceLoader != null) {
            sb.append("yaml");
        }
        if (propertiesPropertySourceLoader != null) {
            sb.append("[properties]");
        }
        return sb.toString();
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public Collection<BaseTccPropertySource> getTccPropertySources() {
        Collection propertySources = tccPropertySources.getPropertySources();
        return ((Collection<BaseTccPropertySource>) propertySources);
    }
}
